package model.tests;

import model.Environment;
import model.Interpreter;
import model.SyntaxErrorException;


public class InterpreterTester extends junit.framework.TestCase {

    private Interpreter interpreter;
    private CommandableTester commandable;

    public InterpreterTester() {
        commandable = new CommandableTester();
        interpreter = new Interpreter(new Environment(null));
    }

    /*
     * Exception Handling tests
     */

    private void assertSeesSyntaxErrorException(String cmd)
                                                           throws SyntaxErrorException {
        try {
            interpreter.interpretAndExecute(cmd);
            fail("Did not catch SyntaxErrorException");
        } catch (SyntaxErrorException ex) {
            System.out.println(ex.getMessage());
        }
    }

    public void testGibberish() throws SyntaxErrorException {
        assertSeesSyntaxErrorException("asd;flkaenrf;");
    }

    public void testWrongNumberOfArguments() throws SyntaxErrorException {
        assertSeesSyntaxErrorException("SUM 1");
    }

    public void testOpenBrackets() throws SyntaxErrorException {
        assertSeesSyntaxErrorException("SUM 2 [ SUM 1 1");
    }

    public void testUnknownVariable() throws SyntaxErrorException {
        assertSeesSyntaxErrorException("SUM 2 :var");
    }

    public void testBadVariableNameWithNumber() throws SyntaxErrorException {
        assertSeesSyntaxErrorException("MAKE :2 2");
    }

    public void testBadVariableNameWithoutColon() throws SyntaxErrorException {
        assertSeesSyntaxErrorException("MAKE :2 2");
    }

    /*
     * Literal tests
     */

    public void testLiteral() throws SyntaxErrorException {
        assertEquals(interpreter.interpretAndExecute("2"), 2.0D);
    }

    /*
     * Math tests
     */
    public void testSum() throws SyntaxErrorException {
        assertEquals(interpreter.interpretAndExecute("SUM 1 1"), 2.0D);
    }

    public void testNestedSum() throws SyntaxErrorException {
        assertEquals(interpreter.interpretAndExecute("SUM SUM 1 2 3"), 6.0D);
    }

    public void testDifference() throws SyntaxErrorException {
        assertEquals(interpreter.interpretAndExecute("DIFFERENCE 10 2"), 8.0D);
    }

    public void testProduct() throws SyntaxErrorException {
        assertEquals(interpreter.interpretAndExecute("PRODUCT 2 3"), 6.0D);
    }

    public void testQuotient() throws SyntaxErrorException {
        assertEquals(interpreter.interpretAndExecute("QUOTIENT 3 2"), 1.5D);
    }

    public void testRemainder() throws SyntaxErrorException {
        assertEquals(interpreter.interpretAndExecute("REMAINDER 10 3"), 1.0D);
    }

    public void testMinus() throws SyntaxErrorException {
        assertEquals(interpreter.interpretAndExecute("MINUS 1"), -1.0D);
    }

    public void testRandom() throws SyntaxErrorException {
        // TODO: make rigorous
        assertTrue(interpreter.interpretAndExecute("RANDOM 100") < 100D);
        assertTrue(interpreter.interpretAndExecute("RANDOM 100") > 0D);
    }

    /*
     * 
     * Boolean Tests
     */

    public void testLess() throws SyntaxErrorException {
        assertEquals(interpreter.interpretAndExecute("LESS? 2 3"), 1.0D);
        assertEquals(interpreter.interpretAndExecute("LESS? 2 2"), 0.0D);
        assertEquals(interpreter.interpretAndExecute("LESS? 3 2"), 0.0D);
    }

    public void testLessP() throws SyntaxErrorException {
        assertEquals(interpreter.interpretAndExecute("LESSP 2 3"), 1.0D);
        assertEquals(interpreter.interpretAndExecute("LESSP 2 2"), 0.0D);
        assertEquals(interpreter.interpretAndExecute("LESSP 3 2"), 0.0D);
    }

    public void testGreater() throws SyntaxErrorException {
        assertEquals(interpreter.interpretAndExecute("GREATER? 2 3"), 0.0D);
        assertEquals(interpreter.interpretAndExecute("GREATER? 2 2"), 0.0D);
        assertEquals(interpreter.interpretAndExecute("GREATER? 3 2"), 1.0D);
    }

    public void testGreaterP() throws SyntaxErrorException {
        assertEquals(interpreter.interpretAndExecute("GREATERP 2 3"), 0.0D);
        assertEquals(interpreter.interpretAndExecute("GREATERP 2 2"), 0.0D);
        assertEquals(interpreter.interpretAndExecute("GREATERP 3 2"), 1.0D);
    }

    public void testEqual() throws SyntaxErrorException {
        assertEquals(interpreter.interpretAndExecute("EQUAL? 2 2"), 1.0D);
        assertEquals(interpreter.interpretAndExecute("EQUAL? 3 2"), 0.0D);
    }

    public void testEqualP() throws SyntaxErrorException {
        assertEquals(interpreter.interpretAndExecute("EQUALP 2 2"), 1.0D);
        assertEquals(interpreter.interpretAndExecute("EQUALP 3 2"), 0.0D);
    }

    public void testNotEqual() throws SyntaxErrorException {
        assertEquals(interpreter.interpretAndExecute("NOTEQUAL? 2 2"), 0.0D);
        assertEquals(interpreter.interpretAndExecute("NOTEQUAL? 3 2"), 1.0D);
    }

    public void testNotEqualP() throws SyntaxErrorException {
        assertEquals(interpreter.interpretAndExecute("NOTEQUALP 2 2"), 0.0D);
        assertEquals(interpreter.interpretAndExecute("NOTEQUALP 3 2"), 1.0D);
    }

    public void testAnd() throws SyntaxErrorException {
        assertEquals(interpreter.interpretAndExecute("AND 2 2"), 1.0D);
        assertEquals(interpreter.interpretAndExecute("AND 1 0"), 0.0D);
        assertEquals(interpreter.interpretAndExecute("AND 0 1"), 0.0D);
        assertEquals(interpreter.interpretAndExecute("AND 1 1"), 1.0D);
    }

    public void testOr() throws SyntaxErrorException {
        assertEquals(interpreter.interpretAndExecute("OR 2 2"), 1.0D);
        assertEquals(interpreter.interpretAndExecute("OR 1 0"), 1.0D);
        assertEquals(interpreter.interpretAndExecute("OR 0 1"), 1.0D);
        assertEquals(interpreter.interpretAndExecute("OR 1 1"), 1.0D);
    }

    public void testNot() throws SyntaxErrorException {
        assertEquals(interpreter.interpretAndExecute("NOT 0"), 1.0D);
        assertEquals(interpreter.interpretAndExecute("NOT 1"), 0.0D);
    }

    /*
     * Control Flow tests
     */

    public void testSimpleBracket() throws SyntaxErrorException {
        assertEquals(interpreter.interpretAndExecute("[ 1 ]"), 1.0D);
    }

    public void testBracketList() throws SyntaxErrorException {
        assertEquals(interpreter.interpretAndExecute("[ 1 2 3 ]"), 3.0D);
    }

    public void testBracketListWithSum() throws SyntaxErrorException {
        assertEquals(interpreter.interpretAndExecute("[ 1 SUM 2 3 ]"), 5.0D);
    }

    public void testIfWhenTrue() throws SyntaxErrorException {
        assertEquals(interpreter.interpretAndExecute("IF 1 [ 1 ]"), 1.0D);
    }

    public void testIfWhenFalse() throws SyntaxErrorException {
        assertEquals(interpreter.interpretAndExecute("IF 0 [ 1 ]"), 0.0D);
    }

    public void testNestedIf() throws SyntaxErrorException {
        assertEquals(interpreter.interpretAndExecute("IF 1 [ IF 1 [ 1 ] ]"),
                     1.0D);
    }

    public void testIfElseWhenTrue() throws SyntaxErrorException {
        assertEquals(interpreter.interpretAndExecute("IFELSE 1 [ 1 ] [ 2 ]"),
                     1.0D);
    }

    public void testIfElseWhenFalse() throws SyntaxErrorException {
        assertEquals(interpreter.interpretAndExecute("IFELSE 0 [ 1 ] [ 2 ]"),
                     2.0D);
    }

    // TODO: make sure that it is actually called 5 times
    public void testRepeat() throws SyntaxErrorException {
        assertEquals(interpreter.interpretAndExecute("REPEAT 5 [ FD 5 ]"), 5.0D);
    }

    public void testTo() throws SyntaxErrorException {
        assertEquals(interpreter.interpretAndExecute(
                "TO MULT [ :x :y ] [ PRODUCT :x :y ]"),
                     1.0D);
        assertEquals(interpreter.interpretAndExecute("MULT 2 3"),
                     6.0D);
        // don't keep function after test completes
        interpreter = new Interpreter(new Environment(null));
    }

    /*
     * Variable tests
     */

    public void testMake() throws SyntaxErrorException {
        assertEquals(interpreter.interpretAndExecute("MAKE :var 1"),
                     1.0D);
        assertEquals(interpreter.interpretAndExecute(":var"),
                     1.0D);
        // don't keep variable after test completes
        interpreter = new Interpreter(new Environment(null));
    }

    public void testScopeWithinBrackets() throws SyntaxErrorException {
        assertEquals(interpreter
                             .interpretAndExecute("MAKE :var 1 [ MAKE :b 2 :b ]"),
                     2.0D);
        // don't keep variable after test completes
        interpreter = new Interpreter(new Environment(null));
    }

    public void testMultipleScope() throws SyntaxErrorException {
        assertEquals(interpreter
                             .interpretAndExecute("MAKE :var 1 [ MAKE :b 2 :b ]"),
                     2.0D);
        // don't keep variable after test completes
        interpreter = new Interpreter(new Environment(null));
    }

    public void testScopeOverwrite() throws SyntaxErrorException {
        assertEquals(interpreter
                             .interpretAndExecute("MAKE :var 1 [ MAKE :var 2 :var ]"),
                     2.0D);
        // don't keep variable after test completes
        interpreter = new Interpreter(new Environment(null));
    }

    public void testScopeDoesntOverwriteGlobals() throws SyntaxErrorException {
        assertEquals(interpreter.interpretAndExecute("MAKE :var 1 [ MAKE :var 2 ] :var"),
                     1.0D);

        // don't keep variable after test completes
        interpreter = new Interpreter(new Environment(null));

    }

    public void testLocalScopeWithinBracket() throws SyntaxErrorException {
        assertEquals(interpreter.interpretAndExecute("[ MAKE :var 2 :var ]"),
                     2.0D);

        // don't keep variable after test completes
        interpreter = new Interpreter(new Environment(null));

    }

    public void testVariablesWithinBrackets() throws SyntaxErrorException {
        assertEquals(interpreter.interpretAndExecute(" MAKE :var 2 [ :var ] "),
                     2.0D);

        // don't keep variable after test completes
        interpreter = new Interpreter(new Environment(null));
    }

    public void testBracketMaintainsGlobals() throws SyntaxErrorException {
        assertEquals(interpreter.interpretAndExecute(" MAKE :var 2 [ :var ]"),
                     2.0D);

        // don't keep variable after test completes
        interpreter = new Interpreter(new Environment(null));

    }

    /*
     * Commandable command tests
     */

    public void testForwardShort() throws SyntaxErrorException {
        assertEquals(interpreter.interpretAndExecute("FD 10"), 10D);
        assertEquals(commandable.getMoveValue(), 10);
        assertTrue(commandable.isMoveCalled());
        commandable.resetCalls();
    }

    public void testForwardLong() throws SyntaxErrorException {
        assertEquals(interpreter.interpretAndExecute("FORWARD 10"), 10D);
        assertEquals(commandable.getMoveValue(), 10);
        assertTrue(commandable.isMoveCalled());
        commandable.resetCalls();
    }

    public void testBackwardShort() throws SyntaxErrorException {
        assertEquals(interpreter.interpretAndExecute("BK 10"), 10D);
        assertEquals(commandable.getMoveValue(), -10);
        assertTrue(commandable.isMoveCalled());
        commandable.resetCalls();
    }

    public void testBackwardLong() throws SyntaxErrorException {
        assertEquals(interpreter.interpretAndExecute("BACK 10"), 10D);
        assertEquals(commandable.getMoveValue(), -10);
        assertTrue(commandable.isMoveCalled());
        commandable.resetCalls();
    }

    public void testLeftShort() throws SyntaxErrorException {
        assertEquals(interpreter.interpretAndExecute("LT 10"), 10D);
        assertEquals(commandable.getRotateValue(), -10D);
        assertTrue(commandable.isRotateCalled());
        commandable.resetCalls();
    }

    public void testLeftLong() throws SyntaxErrorException {
        assertEquals(interpreter.interpretAndExecute("LEFT 10"), 10D);
        assertEquals(commandable.getRotateValue(), -10D);
        assertTrue(commandable.isRotateCalled());
        commandable.resetCalls();
    }

    public void testRightShort() throws SyntaxErrorException {
        assertEquals(interpreter.interpretAndExecute("RT 10"), 10D);
        assertEquals(commandable.getRotateValue(), 10D);
        assertTrue(commandable.isRotateCalled());
        commandable.resetCalls();
    }

    public void testRightLong() throws SyntaxErrorException {
        assertEquals(interpreter.interpretAndExecute("RIGHT 10"), 10D);
        assertEquals(commandable.getRotateValue(), 10D);
        assertTrue(commandable.isRotateCalled());
        commandable.resetCalls();
    }

    public void testSetHeadingShort() throws SyntaxErrorException {
        assertEquals(interpreter.interpretAndExecute("SETH 10"), 10D);
        assertEquals(commandable.getHeadingValue(), 10D);
        assertTrue(commandable.isSetHeadingCalled());
        commandable.resetCalls();
    }

    public void testSetHeadingLong() throws SyntaxErrorException {
        assertEquals(interpreter.interpretAndExecute("SETHEADING 10"), 10D);
        assertEquals(commandable.getHeadingValue(), 10D);
        assertTrue(commandable.isSetHeadingCalled());
        commandable.resetCalls();
    }

    public void testTowards() throws SyntaxErrorException {
        commandable.setPosition(0, 0);
        commandable.setHeading(0);

        assertEquals(interpreter.interpretAndExecute("TOWARDS 10 10"), 45D);
        assertTrue(commandable.isSetHeadingCalled());
        commandable.resetCalls();
    }

    public void testSetXY() throws SyntaxErrorException {
        commandable.setPosition(0, 0);
        interpreter.interpretAndExecute("HOME");
        assertEquals(interpreter.interpretAndExecute("SETXY 10 0"), 10D);
        assertTrue(commandable.isSetPositionCalled());
        commandable.resetCalls();

        commandable.setPosition(0, 0);
        interpreter.interpretAndExecute("SETXY 0 10");
        assertTrue(commandable.isSetPositionCalled());
        commandable.resetCalls();
    }

    public void testPenDownShort() throws SyntaxErrorException {
        assertEquals(interpreter.interpretAndExecute("PD"), 1D);
        assertTrue(commandable.isSetPenDownCalled());
        commandable.resetCalls();
    }

    public void testPenDownLong() throws SyntaxErrorException {
        assertEquals(interpreter.interpretAndExecute("PENDOWN"), 1D);
        assertTrue(commandable.isSetPenDownCalled());
        commandable.resetCalls();
    }

    public void testPenUpShort() throws SyntaxErrorException {
        assertEquals(interpreter.interpretAndExecute("PU"), 0D);
        assertTrue(commandable.isSetPenUpCalled());
        commandable.resetCalls();
    }

    public void testPenUpLong() throws SyntaxErrorException {
        assertEquals(interpreter.interpretAndExecute("PENUP"), 0D);
        assertTrue(commandable.isSetPenUpCalled());
        commandable.resetCalls();
    }

    public void testShowTurtleShort() throws SyntaxErrorException {
        assertEquals(interpreter.interpretAndExecute("ST"), 1D);
        assertTrue(commandable.isMakeVisibleCalled());
        commandable.resetCalls();
    }

    public void testShowTurtleLong() throws SyntaxErrorException {
        assertEquals(interpreter.interpretAndExecute("SHOWTURTLE"), 1D);
        assertTrue(commandable.isMakeVisibleCalled());
        commandable.resetCalls();
    }

    public void testHideTurtleShort() throws SyntaxErrorException {
        assertEquals(interpreter.interpretAndExecute("HT"), 0D);
        assertTrue(commandable.isMakeInvisibleCalled());
        commandable.resetCalls();
    }

    public void testHideTurtleLong() throws SyntaxErrorException {
        assertEquals(interpreter.interpretAndExecute("HIDETURTLE"), 0D);
        assertTrue(commandable.isMakeInvisibleCalled());
        commandable.resetCalls();
    }

    public void testHome() throws SyntaxErrorException {
        commandable.setPosition(10, 0);
        assertEquals(interpreter.interpretAndExecute("HOME"), 10D);
        assertTrue(commandable.isSetPositionCalled());
        commandable.resetCalls();

        commandable.setPosition(0, 10);
        assertEquals(interpreter.interpretAndExecute("HOME"), 10D);
        assertTrue(commandable.isSetPositionCalled());
        commandable.resetCalls();
    }

    public void testClearScreenShort() throws SyntaxErrorException {
        commandable.setPosition(0, 10);
        assertEquals(interpreter.interpretAndExecute("CS"), 10D);
        assertTrue(commandable.isResetCalled());
        commandable.resetCalls();
    }

    public void testClearScreenLong() throws SyntaxErrorException {
        commandable.setPosition(0, 10);
        assertEquals(interpreter.interpretAndExecute("CLEARSCREEN"), 10D);
        assertTrue(commandable.isResetCalled());
        commandable.resetCalls();
    }
}
